import sys
import os
import platform
import struct
import getpass

from made.util import misc
from made.contrib import compilersettings, compilercommands, tasks
from made import runtime

import version

def initialize(unexpanded_lib_deps={}, cpp_compiler_chain=None):
    toplevel_output_dir = "%s/out" % runtime.toplevel_madefile().dir
    build_temp_dir = "%s/build_temp" % runtime.toplevel_madefile().dir
    kwargs = misc.DictWithAttrs(object_file_output_dir=build_temp_dir)

    kwargs.update(dict(
        dynamic_library_output_dir = toplevel_output_dir + (
            "/bin" if platform.system() == "Windows" else "/lib"),
        static_library_output_dir = (
            "%s/usolib" % kwargs.object_file_output_dir),
        binary_output_dir = "%s/bin" % toplevel_output_dir,
        import_lib_output_dir = ("%s/importlib" % build_temp_dir
            if platform.system() == "Windows" else None),
        version=version.version,
        cpp_compiler_chain=cpp_compiler_chain))

    compilersettings.initialize(**kwargs)

    state = compilersettings.push_state()
    _push_compiler_options(state)
    _push_defines(state)
    _push_include_dirs(state)
    _push_link_dirs(state)
    _push_msvc_default_libs(state)

    tasks.install_user_config(unexpanded_lib_deps)

    # Add tasks to create the output directories.
    # TODO: Create clean tasks to remove the output directories.  To remove
    #       the output directory, we need a way of only running it after
    #       the subdirectories have been removed.  Maybe we need a
    #       files_that_cannot_exist list?
    # TODO: Do this inside compilersettings, so it doesn't have to be
    #       duplicated between different madeconfig files.
    dirs = [toplevel_output_dir]
    for dir in (kwargs.dynamic_library_output_dir,
            kwargs.static_library_output_dir, kwargs.binary_output_dir,
            kwargs.object_file_output_dir, kwargs.import_lib_output_dir):
        if dir is not None and dir not in dirs:
            dirs.append(dir)

    for dir in dirs:
        runtime.user_config.mkdir(dir)

def _make_subdir(dir):
    """Create a task that makes a directory and will not run until the parent
    directory exists.
    """
    # TODO: Now that we do "mkdir -p", we don't need the parent level directory
    #       stuff any more.
    runtime.user_config.mkdir(dir, os.path.dirname(dir))

def _push_compiler_options(state):
    if compilersettings.cpp_compiler_chain.name == "gcc":
        _push_gcc_compiler_options(state)

    if _is_using_msvc():
        _push_msvc_compiler_options(state)

def _push_gcc_compiler_options(state):
    state.add_extra_options((
            "-fmessage-length=0", "-Wno-parentheses", "-Wno-sign-compare",
            "-Wno-uninitialized", "-Wunused", "-Wno-unused-parameter",
            "-Werror=sequence-point", "-Wno-deprecated"),
        compilercommands.operation.compile)

    state.add_extra_options(
        ("-Woverloaded-virtual", "-Wno-reorder"),
        compilercommands.operation.compile,
        language=compilercommands.language.cpp)

    state.add_extra_options(
        ("-fno-strict-aliasing",),
        compilercommands.operation.compile,
        is_debug=False)

def _push_msvc_compiler_options(state):
    state.add_extra_options((
            "-nologo", # suppress printing compiler info
            "-wd4244", # double to float conversion
            "-wd4305", # double to float conversion
            "-wd4996", # deprecation
            "-wd4355", # this used as initializer to base class
            "-wd4018", # signed/unsigned mismatch
            "-wd4800", # forcing types to bools
            "-wd4345", # simple aggregates and inplace constructors
            "-wd4103", # alignment changes afer including header
            "-w14390", # enable: empy control statement warning
            "-w14101", # enable: unreferenced local variable
            "-we4700", # warning as error: uninitialized local variable
        ),
        compilercommands.operation.compile)

    state.add_extra_options((
            "-Zc:forScope", # proper for loop scoping
            "-EHsc",        # use C++ exceptions, but not from extern C's
            "-GR"           # enable RTTI
        ),
        compilercommands.operation.compile,
        language=compilercommands.language.cpp)

    state.add_extra_options((
            "-STACK:67108864", # 64MB stack size
        ),
        compilercommands.operation.link_binary)

def _push_defines(state):
    state.defines.extend((
        'VERSION="%s"' % version.version,
        'USERNAME="%s"' % getpass.getuser(),
        "ENABLE_UI_THREADS",
        "HAS_PYTHON",
        "BT_USE_DOUBLE_PRECISION",
    ))

    if _is_using_msvc():
        state.defines.extend((
            "WIN32", "_WIN32_WINNT=0x0501", "WINVER=0x0501",
            "_CRT_SECURE_NO_DEPRECATE", "NOMINMAX",
            "NEED_SPECIALIZATION_STORAGE",
            "PNG_USER_CONFIG", "BOOST_IOSTREAMS_NO_LIB",
            "BT_USE_DOUBLE_PRECISION"))
    else:
        state.defines.append(
            "LINUX" if platform.system() == "Linux" else "MBSD")
        state.defines.extend(("_GNU_SOURCE", "USE_PTHREADS", "_REENTRANT",
            "_FILE_OFFSET_BITS=64", "GCC3"))
        if compilersettings.cpp_compiler_chain.gcc_version()[0] >= 4:
            state.defines.append("GCC4")

    if struct.calcsize("P") == 8:
        state.defines.extend(("AMD64", "SIZEOF_VOID_P=8"))
    else:
        state.defines.append("SIZEOF_VOID_P=4")

    state.defines.append(
        "SESI_LITTLE_ENDIAN" if sys.byteorder == "little"
        else "SESI_BIG_ENDIAN")

    state.defines.append(lambda is_debug, **kwargs:
        ("UT_ASSERT_LEVEL=2", "UT_DEBUG", "UI_DEBUG")
        if is_debug else ("NDEBUG", "UT_ASSERT_LEVEL=0"))

    if _is_using_msvc():
        def get_msvc_export_defines(is_shared_library, library_name, **kwargs):
            if is_shared_library is None:
                return ()
            return (("EXPORT_%s" % library_name if is_shared_library
                else "MAKING_STATIC"),)
        state.defines.append(get_msvc_export_defines)

def _push_include_dirs(state):
    state.include_dirs.append("%s/lib" % runtime.toplevel_madefile().dir)
    if _is_using_msvc():
        state.include_dirs.extend(
            "%s/%s" % (compilersettings.cpp_compiler_chain.msvc_dir, subdir)
            for subdir in ("include", "PlatformSDK/Include"))

def _push_link_dirs(state):
    if platform.system() == "Linux":
        state.link_dirs.extend(("/usr/X11R6/lib64", "/usr/X11R6/lib"))

def _push_msvc_default_libs(state):
    state.msvc_default_libs.extend((
        "MPR", "GLAUX", "OLE32", "COMDLG32", "VERSION", "UUID", "GLU32",
        "GDI32", "WINMM", "VFW32", "ADVAPI32", "WS2_32", "USER32", "NETAPI32",
        "OPENGL32", "SHELL32", "IPHlpAPI", "DelayImp", "COMCTL32",
    ))

def _is_using_msvc():
    return compilersettings.cpp_compiler_chain.name == "msvc"

#-----------------------------------------------------------------------------

# TODO: Use this function.  We currently don't invoke makedepend.
def get_extra_makedepend_defines():
    if not _is_using_msvc():
        return ()

    return ("_MSC_VER=1400", "_MT", "_CPPUNWIND", "_CPPRTTI",
        "WIN32_LEAN_AND_MEAN", "NULL=0" "__RPC_H__", "_M_IX86",
        "PLATFORM_OPEN_GL", "DEFINE_GUID")

